#include "System/I2C/soft_i2c.h"

/**
 * @brief       初始化软件I2C
 * @param       i2c_id: I2C接口ID号
 * @retval      无
 */
void soft_i2c_init(uint8_t i2c_id)
{
    SDA_SET(i2c_id, 1);
    SCL_SET(i2c_id, 1);
}

/**
 * @brief       产生I2C起始信号
 * @param       i2c_id: I2C接口ID号
 * @retval      无
 */
void i2c_start(uint8_t i2c_id)
{
    SDA_OUT(i2c_id);
    SDA_SET(i2c_id, 1);
    SCL_SET(i2c_id, 1);
    I2C_DELAY_US(i2c_id);
    SDA_SET(i2c_id, 0); /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
    I2C_DELAY_US(i2c_id);
    SCL_SET(i2c_id, 0); /* 钳住I2C总线，准备发送或接收数据 */
}

/**
 * @brief       产生I2C停止信号
 * @param       i2c_id: I2C接口ID号
 * @retval      无
 */
void i2c_stop(uint8_t i2c_id)
{
    SDA_OUT(i2c_id);
    SDA_SET(i2c_id, 0); /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
    SCL_SET(i2c_id, 0); /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
    I2C_DELAY_US(i2c_id);
    SCL_SET(i2c_id, 1);
    SDA_SET(i2c_id, 1); /* 发送I2C总线结束信号 */
    I2C_DELAY_US(i2c_id);
}

/**
 * @brief       等待应答信号到来
 * @param       i2c_id: I2C接口ID号
 * @retval      1，接收应答失败
 *              0，接收应答成功
 */
uint8_t i2c_wait_ack(uint8_t i2c_id)
{
    uint32_t waittime = 0;

    SDA_IN(i2c_id);     /* 设置SDA为输入模式 */
    SDA_SET(i2c_id, 1); /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
    I2C_DELAY_US(i2c_id);
    SCL_SET(i2c_id, 1); /* SCL=1, 此时从机可以返回ACK */
    I2C_DELAY_US(i2c_id);
    while (SDA_READ(i2c_id)) /* 等待应答 */
    {
        waittime++;

        if (waittime > 0xFF)
        {
            i2c_stop(i2c_id);
            return 1;
        }
    }
    SCL_SET(i2c_id, 0); /* SCL=0, 结束ACK检查 */
    return 0;
}

/**
 * @brief       产生ACK应答
 * @param       i2c_id: I2C接口ID号
 * @retval      无
 */
void i2c_ack(uint8_t i2c_id)
{
    SCL_SET(i2c_id, 0); /* SCL 0 -> 1 时 SDA = 0,表示应答 */
    SDA_OUT(i2c_id);
    I2C_DELAY_US(i2c_id);
    SDA_SET(i2c_id, 0); /* SCL 0 -> 1 时 SDA = 0,表示应答 */
    I2C_DELAY_US(i2c_id);
    SCL_SET(i2c_id, 1); /* 产生一个时钟 */
    I2C_DELAY_US(i2c_id);
    SCL_SET(i2c_id, 0);
}

/**
 * @brief       不产生ACK应答
 * @param       i2c_id: I2C接口ID号
 * @retval      无
 */
void i2c_nack(uint8_t i2c_id)
{
    SCL_SET(i2c_id, 0); /* 产生一个时钟 */
    SDA_OUT(i2c_id);
    SDA_SET(i2c_id, 1); /* SCL 0 -> 1  时 SDA = 1,表示不应答 */
    I2C_DELAY_US(i2c_id);
    SCL_SET(i2c_id, 1); /* 产生一个时钟 */
    I2C_DELAY_US(i2c_id);
    SCL_SET(i2c_id, 0);
}

/**
 * @brief       I2C发送一个字节
 * @param       i2c_id: I2C接口ID号
 * @param       data: 要发送的数据
 * @retval      无
 */
void i2c_send_byte(uint8_t i2c_id, const uint8_t data)
{
    SDA_OUT(i2c_id);
    SCL_SET(i2c_id, 0); /* 产生一个时钟 */
    for (uint8_t i = 0; i < 8; i++)
    {
        SDA_SET(i2c_id, (((data << i) & 0x80) >> 7));

        I2C_DELAY_US(i2c_id);
        SCL_SET(i2c_id, 1);
        I2C_DELAY_US(i2c_id);
        SCL_SET(i2c_id, 0);
    }
}

/**
 * @brief       I2C读取一个字节
 * @param       i2c_id: I2C接口ID号
 * @param       ack:  ack=1时，发送ack; ack=0时，发送nack
 * @retval      接收到的数据
 */
uint8_t i2c_read_byte(uint8_t i2c_id, uint8_t ack)
{
    uint8_t i, receive = 0;
    SDA_SET(i2c_id, 1);
    SDA_IN(i2c_id);         /* 设置SDA为输入模式 */
    for (i = 0; i < 8; i++) /* 接收1个字节数据 */
    {

        SCL_SET(i2c_id, 0);
        I2C_DELAY_US(i2c_id);
        SCL_SET(i2c_id, 1);
        receive <<= 1; /* 高位先输出,所以先收到的数据位要左移 */

        if (SDA_READ(i2c_id))
        {
            receive++;
        }
        I2C_DELAY_US(i2c_id);
    }
    if (!ack)
    {
        i2c_nack(i2c_id); /* 发送nACK */
    }
    else
    {
        i2c_ack(i2c_id); /* 发送ACK */
    }

    return receive;
}

/**
 * @brief       软件I2C连续写数据
 * @param       i2c_id: I2C接口ID号
 * @param       salve_adress: 从设备地址
 * @param       reg_address: 寄存器地址
 * @param       data: 存储读取数据的缓冲区
 * @param       len: 要读取的数据长度
 * @retval      0，读取成功；1，读取失败
 */
uint8_t soft_i2c_write_data(uint8_t i2c_id, uint8_t slave_addr, uint8_t reg_address, const uint8_t *data, uint8_t len)
{
    // 产生起始信号
    i2c_start(i2c_id);

    // 发送从设备地址 + 写命令
    i2c_send_byte(i2c_id, slave_addr & 0xFE);
    if (i2c_wait_ack(i2c_id))
    {
        i2c_stop(i2c_id);
        I2C_LOG("I2C Write: Wait ACK failed for address 0x%02X\n", slave_addr);
        return 1; // 接收应答失败
    }

    // 发送寄存器地址
    i2c_send_byte(i2c_id, reg_address);
    if (i2c_wait_ack(i2c_id))
    {
        i2c_stop(i2c_id);
        I2C_LOG("I2C Write: Wait ACK failed for register 0x%02X\n", reg_address);
        return 1; // 接收应答失败
    }

    // 发送数据
    for (uint8_t i = 0; i < len; i++)
    {
        i2c_send_byte(i2c_id, data[i]);
        if (i2c_wait_ack(i2c_id))
        {
            i2c_stop(i2c_id);
            I2C_LOG("I2C Write: Wait ACK failed for data 0x%02X\n", data[i]);
            return 1; // 接收应答失败
        }
    }

    // 产生停止信号
    i2c_stop(i2c_id);

    return 0;
}

/**
 * @brief       软件I2C连续读数据
 * @param       i2c_id: I2C接口ID号
 * @param       salve_adress: 从设备地址
 * @param       reg_address: 寄存器地址
 * @param       data: 存储读取数据的缓冲区
 * @param       len: 要读取的数据长度
 * @retval      0，读取成功；1，读取失败
 */
uint8_t soft_i2c_read_data(uint8_t i2c_id, uint8_t slave_addr, uint8_t reg_address, uint8_t *data, uint8_t len)
{
    // 产生起始信号
    i2c_start(i2c_id);

    // 发送从设备地址 + 写命令
    i2c_send_byte(i2c_id, slave_addr & 0xFE);
    if (i2c_wait_ack(i2c_id))
    {
        i2c_stop(i2c_id);
        I2C_LOG("I2C Read: Wait ACK failed for address 0x%02X\n", slave_addr);
        return 1; // 接收应答失败
    }
    // 发送寄存器地址
    i2c_send_byte(i2c_id, reg_address);
    if (i2c_wait_ack(i2c_id))
    {
        i2c_stop(i2c_id);
        I2C_LOG("I2C Read: Wait ACK failed for register 0x%02X\n", reg_address);
        return 1; // 接收应答失败
    }
    // 产生起始信号
    i2c_start(i2c_id);
    // 发送从设备地址 + 读命令
    i2c_send_byte(i2c_id, slave_addr | 0x01);
    if (i2c_wait_ack(i2c_id))
    {
        i2c_stop(i2c_id);
        I2C_LOG("I2C Read: Wait ACK failed for address 0x%02X in read mode\n", slave_addr);
        return 1; // 接收应答失败
    }

    // 读取数据
    for (uint8_t i = 0; i < len; i++)
    {
        data[i] = i2c_read_byte(i2c_id, i < len - 1 ? 1 : 0);
    }

    // 产生停止信号
    i2c_stop(i2c_id);

    return 0; // 读取成功
}

/**
 * @brief       直接读 N 字节序列（无寄存器地址阶段）
 * @param       i2c_id: I2C接口ID号
 * @param       salve_adress: 从设备地址
 * @param       data: 存储读取数据的缓冲区
 * @param       len: 要读取的数据长度
 * @retval      0，读取成功；1，读取失败
 */
uint8_t soft_i2c_read_seq(uint8_t i2c_id, uint8_t slave_addr, uint8_t *data, uint8_t len)
{
    // 产生起始信号
    i2c_start(i2c_id);
    // 发送从设备地址 + 读命令
    i2c_send_byte(i2c_id, slave_addr | 0x01);
    if (i2c_wait_ack(i2c_id))
    {
        i2c_stop(i2c_id);
        I2C_LOG("I2C Read: Wait ACK failed for address 0x%02X in read mode\n", slave_addr);
        return 1; // 接收应答失败
    }
    // 读取数据
    for (uint8_t i = 0; i < len; i++)
    {
        data[i] = i2c_read_byte(i2c_id, i < len - 1 ? 1 : 0);
    }

    // 产生停止信号
    i2c_stop(i2c_id);

    return 0; // 读取成功
}
